/*
 * MIT License
 * 
 * Copyright (c) 2017 wen.gu <454727014@qq.com>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/***************************************************************************
 * Name: posix_thread.c

 *
 * Purpose: posix thread implement
 *
 * Developer:
 *   wen.gu , 2014-12-25
 *
 * TODO: 
 *
 ***************************************************************************/

//inner header file
#include "sys_config.h"


#if defined(HAVE_POSIX_THREAD) && (HAVE_POSIX_THREAD)
//API header file
#include "vos_thread.h"
#include "vos_condition.h"
#include "vos_mutex.h"
#include "general_macro.h"


#include "vos_mem.h"
#include "vos_sys.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <sched.h>
#include <assert.h>
#include <errno.h>



#if defined(_WIN32) //for windows version

#if defined(_WIN64) /** for 64 bits windows */
#pragma comment(lib,"x64/pthreadVC2.lib")
#else
#pragma comment(lib,"pthreadVC2.lib")
#endif

#endif


#define THD_PRINT printf

/***************************************************************************
 *
 * mutex lock API define
 *
 ***************************************************************************/
/**
*@brief  create a mutex lock instance
*
*@param  None.
*
*@retuen if success return mutex lock intstance, else return 'NULL'.
*@see 
*/
GPHD vosMutexCreate(void)
{
    pthread_mutexattr_t attr;
    pthread_mutex_t* mutex = (pthread_mutex_t*)memAlloc(sizeof(pthread_mutex_t));

    if (!mutex)
    {
        THD_PRINT("alloc mutex instance failed\n");
        abort();
    }
    
    if (0 != pthread_mutexattr_init (&attr))
    {
        THD_PRINT("pthread_mutexattr_init failed\n");
        memFree(mutex);
        abort();
    }
#if defined(_GNU_SOURCE) || (XOPEN_SOURCE >= 500)
#ifdef NDEBUG
    pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_FAST_NP);
#else
    pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_ERRORCHECK_NP);
#endif
#endif

    if (0 != pthread_mutex_init (mutex, &attr))
    {
        THD_PRINT("pthread_mutex_init failed\n");
        memFree(mutex);
        abort();
    }
    pthread_mutexattr_destroy( &attr );

    return (GPHD)mutex;
}

/**
*@brief  free a mutex lock instance
*
*@param  mutex [in] a mutex lock instance handle.
*
*@retuen None.
*@see 
*/
void vosMutexFree(GPHD mutex)
{
    if (mutex)
    {
        pthread_mutex_destroy((pthread_mutex_t*)mutex);
        memFree(mutex);
    }
    
}

/**
*@brief  lock on
*
*@param  mutex [in] a mutex lock instance handle.
*
*@retuen None.
*@see 
*/
void vosMutexLock(GPHD mutex)
{
    GS32 ret = pthread_mutex_lock((pthread_mutex_t*)mutex);
    if (0 != ret)
    {
        THD_PRINT("mutex lock faild,ret(%d)\n", ret);
    }
}

/**
*@brief  lock off
*
*@param  mutex [in] a mutex lock instance handle.
*
*@retuen None.
*@see 
*/
void vosMutexUnlock(GPHD mutex)
{
    GS32 ret = pthread_mutex_unlock((pthread_mutex_t*)mutex);
    if (0 != ret)
    {
        THD_PRINT("mutex unlock on faild,ret(%d)\n", ret);
    }
}

/***************************************************************************
 *
 * condition lock API define
 *
 ***************************************************************************/
/**
*@brief  create a condition lock instance
*
*@param  None.
*
*@retuen if success return condition lock intstance, else return 'NULL'.
*@see 
*/
GPHD vosCondCreate(void)
{
    pthread_cond_t* cond = (pthread_cond_t*)memAlloc(sizeof(pthread_cond_t));

    if (!cond)
    {
        THD_PRINT("alloc pthread_cond_t failed\n");
    }
    else
    {
        GS32 ret = pthread_cond_init(cond, NULL) ;
        if (0 != ret)
        {
            THD_PRINT("pthread_cond_init failed,ret(%d)\n", ret);
            memFree(cond);
            cond = NULL;
        }
    }

    return (GPHD)cond;
}

/**
*@brief  free a condition lock instance
*
*@param  cond [in] a condition lock instance handle.
*
*@retuen None.
*@see 
*/
void vosCondFree(GPHD cond)
{
    if (cond)
    {
        pthread_cond_destroy((pthread_cond_t*)cond);
        memFree(cond);
    }    
}

/**
*@brief  condition wait a signal.
*
*@param  cond  [in] a condition lock instance handle.
*@param  mutex [in] a mutex lock instance handle.
*
*@retuen if success return G_OK,else return error code.
*@see 
*/
GS32 vosCondWait(GPHD cond, GPHD mutex)
{
    return (GS32)pthread_cond_wait((pthread_cond_t*)cond, (pthread_mutex_t*)mutex); //todo fix me
}

/**
*@brief  condition wait a signal with timeout
*
*@param  cond  [in] a condition lock instance handle.
*@param  mutex [in] a mutex lock instance handle.
*@param  timeOut [in] wait timeout time,unit in milli second(ms).
*
*@retuen if success return G_OK,else return error code.
*@see 
*/
GS32 vosCondTimedWait(GPHD cond, GPHD mutex, GU32 timeOut)
{
    GS32 ret = G_OK;
    struct timespec ts;
	GS64 curTimeMs = vosSysTimeMs(); //get current time unit in milli second

	curTimeMs += timeOut;
	ts.tv_sec  = curTimeMs / 1000;
	ts.tv_nsec = (curTimeMs % 1000) * 1000;

    ret = pthread_cond_timedwait((pthread_cond_t*)cond, (pthread_mutex_t *)(mutex), &ts);

    if (0 != ret)
    {
        THD_PRINT("cond_timedwait failed,ret(%d)\n", ret);
        return G_FAIL;
    }

    return G_OK;
}

/**
*@brief  condition send a signal 
*
*@param  cond [in] a condition lock instance handle.
*
*@retuen None.
*@see 
*/
void vosCondSignal(GPHD cond)
{
    pthread_cond_signal((pthread_cond_t*)cond); //todo fix me
}

/**
*@brief  condition send broadcast signal 
*
*@param  cond [in] a condition lock instance handle.
*
*@retuen None.
*@see 
*/
void vosCondBroadcast(GPHD cond)
{
    pthread_cond_broadcast((pthread_cond_t*)cond); //todo fix me.
}

/***************************************************************************
 *
 * thread API define
 *
 ***************************************************************************/
typedef struct posixThread_s
{ 
    GBOL  initIsComplete; //true pthread_create run complete,false otherwise.
    void* param;          //thread loop function's parameter
    vosThreadFunc_t loopFunc; 

    pthread_t thd;
    
    GS08 name[THD_NAME_MAX_LEN + 1];
    pthread_cond_t cond;
    pthread_mutex_t lock;
}posixThread_t;

/**
*@brief  thread loop function type
*
*@param  param [in] external handle
*
*@retuen if success exit return G_OK , else return error code.
*@see 
*/
static void *threadFunc(void* param)
{
    posixThread_t* posixThd = (posixThread_t*)param;
    
    if (posixThd)
    {
        THD_PRINT("into thread(%s) function, initIsComplete(%s)\n", 
            posixThd->name, (posixThd->initIsComplete == GFALSE) ? "false" : "true");      
        pthread_mutex_lock(&posixThd->lock);
        while (posixThd->initIsComplete != GTRUE) //todo fixe me ,if cycle forever hanppened
        {
            pthread_cond_wait(&(posixThd->cond), &posixThd->lock);
        }

        pthread_mutex_unlock(&posixThd->lock);

        {
            GCHECK(posixThd->loopFunc);
            THD_PRINT("start thread(%s) loop\n", posixThd->name);
            GS32 ret = posixThd->loopFunc(posixThd->param);
            THD_PRINT("thread(%s) exit with ret(%d)\n",posixThd->name, ret);
        }

        pthread_cond_destroy(&(posixThd->cond));
        pthread_mutex_destroy(&(posixThd->lock));
        memFree(param);    
    }
    else
    {
        THD_PRINT("thread func param is NULL\n");
    }
    return NULL;
}


/**
*@brief  create a thread instance
*
*@param  threadId  [io] thread id output
*@param  loopFunc  [in] thread main loop function.
*@param  loopParam [in] thread loop function's parameter.
*@param  attr      [in] thread attribute.
*
*@retuen if success return G_OK, else return G_FAIL.
*@see
*@note   thread default state(is param attr is NULL) is joinable(mean that, use
*        need call function vosThreadjoin to release thread resource)
*
*/
GS32 vosThreadCreate(vos_thread_id *threadId, vosThreadFunc_t loopFunc, void* loopParam, vos_thread_attr_t* attr)
{
#if defined(_WIN32) /** for windows */
    GS32 policy = SCHED_OTHER;
#else /** other */
    GS32 policy = SCHED_RR;
#endif
    GS32 ret = 0;
    GS32 maxPri = 0;
    GS32 minPri = 0;
    GS32 rangePri = 0;
    struct sched_param schParam = {0};
    pthread_attr_t thdattr;
    posixThread_t* posixThd = NULL;
    GS32 priority = attr->priority;
    

    GCHECK(threadId);
    *threadId = NULL;
    
    posixThd = (posixThread_t*)memAlloc(sizeof(posixThread_t));

    if (!posixThd)
    {
        THD_PRINT("allocate win thread instance failed\n");
        goto ERROR;
    }

    memset(posixThd, 0, sizeof(posixThread_t));
    if (attr && attr->name)
    {
        GS32 len = strlen(attr->name);

        if (len > THD_NAME_MAX_LEN)
        {
            printf("thread name too long\n");
            memcpy(posixThd->name, attr->name, THD_NAME_MAX_LEN);
        }
        else
        {
            memcpy(posixThd->name, attr->name, len);
        }
    }
    else
    {        
        memcpy(posixThd->name, "unkown", 6);
    }
    
    ret = pthread_cond_init(&(posixThd->cond), NULL);
    if (0 != ret)
    {
        THD_PRINT("init condition lock failed,ret(%d),err(%d,%s)\n", ret, errno, strerror(errno));
        goto ERROR;
    }

    ret = pthread_mutex_init(&(posixThd->lock), NULL);
    if (0 != ret)
    {
        THD_PRINT("init mutex lock failed,ret(%d),err(%d,%s)\n", ret, errno, strerror(errno));
        goto ERROR;
    }

    posixThd->param          = loopParam;
    posixThd->loopFunc       = loopFunc;
    posixThd->initIsComplete = GFALSE;

    pthread_attr_init(&thdattr);
    pthread_attr_getschedparam(&thdattr, &schParam);
    
    pthread_attr_setschedpolicy(&thdattr, policy);
    minPri = sched_get_priority_min(policy);
    maxPri = sched_get_priority_max(policy);
    THD_PRINT("thread [%s], policy(%d), min-max[%d-%d], pri(%d)\n", posixThd->name, policy, minPri, maxPri, priority);
    rangePri = maxPri - minPri;
    schParam.sched_priority = priority * rangePri / 100;
    pthread_attr_setschedparam(&thdattr, &schParam);
    //pthread_attr_setscope(&attr, NULL);

    if (attr->joinable == GFALSE)
    {
        pthread_attr_setdetachstate(&thdattr, PTHREAD_CREATE_DETACHED);
    }

    ret = pthread_create(&(posixThd->thd), &thdattr, threadFunc, posixThd);

    pthread_attr_destroy (&thdattr);
    
    if (0 != ret)
    {
        THD_PRINT("===>:pthread create failed,ret(%d),err(%d, %s)\n", ret, errno, strerror(errno));        
        goto ERROR;
    }
    
    *threadId= (vos_thread_id)posixThd->thd;

    pthread_mutex_lock(&posixThd->lock);
    posixThd->initIsComplete = GTRUE;    
    pthread_cond_broadcast(&(posixThd->cond));
    pthread_mutex_unlock(&posixThd->lock);

    return G_OK;

ERROR:
            
    if (posixThd)
    {
        pthread_cond_destroy(&(posixThd->cond));
        pthread_mutex_destroy(&(posixThd->lock));
        memFree(posixThd);
    }
   

    return G_FAIL;
}

/**
*@brief  wait for thread to release resource
*
*@param  threadId [in] the id of thread.
*
*@retuen if sucess return G_OK(0),else return error code
*@see
*@note  if thread not in joinable state,then will be return 'G_ErrorInvalidOperation'
*/
GS32 vosThreadJoin(vos_thread_id threadId)
{
    if (threadId != VTHD_INVALID_TID)
    {
        if (threadId != vosThreadGetId())
        {
            void* retVal = NULL;
            pthread_t thd = (pthread_t)(threadId);
            GS32 ret = pthread_join(thd, &retVal);

            if (ret == 0)
            {
                return G_OK;
            }
            else
            {
                THD_PRINT("===>:pthread join failed,ret(%d),err(%d, %s)\n", ret, errno, strerror(errno));
                
                if (ret == EINVAL)
                {
                    return G_ErrorInvalidOperation;
                }
                else if (ret == EDEADLK)
                {
                    return G_ErrorWouldBlock;
                }
            }
        }
        else
        {
            THD_PRINT("can not call(%s) in thread self\n", __FUNCTION__);
            return G_ErrorWouldBlock;
        }

    }

    return G_FAIL;
}


///////////////////////////////////////////////////////////////////////////
/**
*@brief  get thread ID
*
*@param
*
*@retuen return thread ID
*@see
*/
vos_thread_id vosThreadGetId(void)
{
    return (vos_thread_id)pthread_self();
}

#endif
